using System;
using System.Collections.Generic;
using System.Text;
using System.Xml;
using System.Net;
using System.IO;

namespace DiggTools
{
    /// <summary>
    /// This class houses common operations about stories that are supported by the Digg API.
    /// </summary>
    public class StoryFactory
    {
        /// <summary>
        /// Constructor which sets the XML Document that will be used.
        /// </summary>
        public StoryFactory()
        {
            theXmlDocument = new XmlDocument();

            _defaultSearchOptions = new SearchOptions();
            _defaultSearchOptions.SetCount(_defaultNumStories);
        }

        /// <summary>
        /// Retrieves story information from Digg and wraps each story into a Story object.
        /// The collection returns the number of stories specified by the <paramref name="numStories"/> parameter
        /// and filters the query according to the <paramref name="filter"/> parameter
        /// </summary>
        /// <param name="filter">The filter to pass to the query on the call to Digg services</param>
        /// <param name="theOptions">The options for the query</param>
        /// <returns>A List of Stories. The number of stories is determined by <paramref name="numStories"/> and are filtered according to <paramref name="filter"/></returns>
        private List<Story> GetStories(string filter, SearchOptions theOptions)
        {
            List<Story> theStories = new List<Story>();
            String diggStoriesConnectionString = "http://services.digg.com/stories"+filter+parseSearchOptions(theOptions);
            
            //Console.WriteLine(diggStoriesConnectionString);
            WebRequest theRequest = WebRequest.Create(diggStoriesConnectionString);
            theRequest.Timeout = _timeout;
            try
            {
                Stream theStream = theRequest.GetResponse().GetResponseStream();

                try
                {
                    theXmlDocument.Load(theStream);
                }
                catch (XmlException e) { Console.WriteLine(e.Message); }

                XmlNodeList storyNodes = theXmlDocument.GetElementsByTagName("story");
                foreach (XmlNode theNode in storyNodes)
                {
                    int theId = Convert.ToInt32(theNode.Attributes["id"].InnerText);
                    String theLink = theNode.Attributes["link"].InnerText;
                    double theSubmitDateNumber = Convert.ToDouble(theNode.Attributes["submit_date"].InnerText);
                    DateTime theSubmitDate = (new DateTime(1970, 1, 1, 0, 0, 0)).AddSeconds(theSubmitDateNumber);
                    
                    int theDigs = Convert.ToInt32(theNode.Attributes["digs"].InnerText);
                    int theComments = Convert.ToInt32(theNode.Attributes["comments"].InnerText);
                    String theHref = theNode.Attributes["href"].InnerText;
                    String theStatus = theNode.Attributes["status"].InnerText;

                    String theTitle = "";
                    String theDescription = "";
                    String theUser = "";
                    String theTopicName = "";
                    String theTopicShortName = "";
                    String theContainerName = "";
                    String theContainerShortName = "";
                    XmlNodeList childNodes = theNode.ChildNodes;
                    foreach (XmlNode childNode in childNodes)
                    {
                        if (childNode.Name.Equals("title"))
                        {
                            theTitle = childNode.InnerText;
                        }
                        else if (childNode.Name.Equals("description"))
                        {
                            theDescription = childNode.InnerText;
                        }
                        else if (childNode.Name.Equals("user"))
                        {
                            theUser = childNode.Attributes["name"].InnerText;
                        }
                        else if (childNode.Name.Equals("topic"))
                        {
                            theTopicName = childNode.Attributes["name"].InnerText;
                            theTopicShortName = childNode.Attributes["short_name"].InnerText;
                        }
                        else if (childNode.Name.Equals("container"))
                        {
                            theContainerName = childNode.Attributes["name"].InnerText;
                            theContainerShortName = childNode.Attributes["short_name"].InnerText;
                        }

                    }

                    Story theStory = new Story(theId, theLink, theSubmitDate, theDigs, 
                                               theComments, theHref, theStatus, theTitle, 
                                               theDescription, theUser, theTopicName, theTopicShortName, 
                                               theContainerName, theContainerShortName);
                    theStories.Add(theStory);
                }
            }
            catch (WebException)
            {
                return null;
            }

            return theStories;
        }

        private String parseSearchOptions(SearchOptions theOptions)
        {
            int theCount = theOptions.GetCount();
            DateTime theMaxDate = theOptions.GetMaxDate();
            DateTime theMinDate = theOptions.GetMinDate();
            int theOffset = theOptions.GetOffset();
            DateTime theTimestamp = theOptions.GetTimestamp();
            String theSortFilter = theOptions.GetSortFilter();

            String theOptionsString = "?";

            if (theCount != -1)
            {
                theOptionsString += "count=" + Convert.ToString(theCount);
            }

            if (!theMinDate.Equals(new DateTime(1970, 1, 1, 0, 0, 0)))
            {
                if (!theOptionsString.Equals("?"))
                {
                    theOptionsString += "&";
                }

                int theSeconds = Convert.ToInt32((theMinDate - new DateTime(1970, 1, 1, 0, 0, 0)).TotalSeconds);
                theOptionsString += "min_date=" + Convert.ToString(theSeconds);
            }

            if (!theMaxDate.Equals(new DateTime(1970, 1, 1, 0, 0, 0)))
            {
                if (!theOptionsString.Equals("?"))
                {
                    theOptionsString += "&";
                }

                int theSeconds = Convert.ToInt32((theMaxDate - new DateTime(1970, 1, 1, 0, 0, 0)).TotalSeconds);
                theOptionsString += "max_date=" + Convert.ToString(theSeconds);
            }

            if (theOffset != -1)
            {
                if (!theOptionsString.Equals("?"))
                {
                    theOptionsString += "&";
                }

                theOptionsString += "offset=" + Convert.ToString(theOffset);
            }

            if (!theTimestamp.Equals(new DateTime(1970, 1, 1, 0, 0, 0)))
            {
                if (!theOptionsString.Equals("?"))
                {
                    theOptionsString += "&";
                }

                int theSeconds = Convert.ToInt32((theMinDate - new DateTime(1970, 1, 1, 0, 0, 0)).TotalSeconds);
                theOptionsString += "timestamp=" + Convert.ToString(theSeconds);
            }

            if (!String.IsNullOrEmpty(theSortFilter))
            {
                if (!theOptionsString.Equals("?"))
                {
                    theOptionsString += "&";
                }

                theOptionsString += "sort=" + theSortFilter;
            }

            return theOptionsString;
        }

        /// <summary>
        /// Returns the total number of stories that are in the Digg database
        /// </summary>
        /// <returns>The total number of stories in the Digg database</returns>
        public int GetTotalNumberOfStories()
        {
            XmlDocument theXmlDocument = new XmlDocument();
            List<Story> theStories = new List<Story>();

            String diggStoriesConnectionString = "http://services.digg.com/stories?count=1";
            WebRequest theRequest = WebRequest.Create(diggStoriesConnectionString);
            theRequest.Timeout = _timeout;
            try
            {
                Stream theStream = theRequest.GetResponse().GetResponseStream();

                try
                {
                    theXmlDocument.Load(theStream);
                }
                catch (XmlException e) { Console.WriteLine(e.Message); }

                XmlNodeList xmlNodes = theXmlDocument.GetElementsByTagName("stories");
                return Convert.ToInt32(xmlNodes[0].Attributes["total"].InnerText);
            }
            catch (WebException)
            {
                return -1;
            }
        }

        /// <summary>
        /// Gets the default number of most recent stories with no filter applied
        /// </summary>
        /// <returns>the default number of most recent stories</returns>
        public List<Story> GetStories()
        {
            return GetStories("", _defaultSearchOptions);
        }

        /// <summary>
        /// returns a list of stories, the amount is determined by <paramref name="numStories"/> with no filter applied
        /// </summary>
        /// <param name="theOptions">The options for the query</param>
        /// <returns>A list of stories a 'Story' objects</returns>
        public List<Story> GetStories(SearchOptions theOptions)
        {
            return GetStories("", theOptions);
        }

        /// <summary>
        /// returns a list of 'popular' (stories in the popular category) stories. The default amount of stories are returned.
        /// </summary>
        /// <returns>A list of stories a 'Story' objects</returns>
        public List<Story> GetPopularStories()
        {
            return GetStories("/popular", _defaultSearchOptions);
        }

        /// <summary>
        /// returns a list of 'popular' (stories in the popular category) stories, the amount is determined by <paramref name="numStories"/>
        /// </summary>
        /// <param name="theOptions">The options for the query</param>
        /// <returns>A list of stories a 'Story' objects</returns>
        public List<Story> GetPopularStories(SearchOptions theOptions)
        {
            return GetStories("/popular", theOptions);
        }

        /// <summary>
        /// returns a list of 'upcoming' (stories in the upcoming category) stories, the amount is determined by <paramref name="numStories"/>
        /// </summary>
        /// <returns>A list of stories a 'Story' objects</returns>
        public List<Story> GetUpcomingStories()
        {
            return GetStories("/upcoming", _defaultSearchOptions);
        }

        /// <summary>
        /// returns a list of 'upcoming' (stories in the upcoming category) stories, the amount is determined by <paramref name="numStories"/>
        /// </summary>
        /// <param name="theOptions">The options for the query</param>
        /// <returns>A list of stories a 'Story' objects</returns>
        public List<Story> GetUpcomingStories(SearchOptions theOptions)
        {
            return GetStories("/upcoming", theOptions);
        }

        /// <summary>
        /// Get the details of the story with the id of <paramref name="storyID"/>
        /// </summary>
        /// <param name="storyID">The ID of the story to get details of</param>
        /// <returns>A 'Story' object filled with all information about the story with id <paramref name="storyID"/></returns>
        public Story GetStoryDetails(int storyID)
        {
            String diggStoriesConnectionString = "http://services.digg.com/stories/"+Convert.ToString(storyID);
            try
            {
                theXmlDocument.Load(diggStoriesConnectionString);
            }
            catch (XmlException e) { Console.WriteLine(e.Message); }

            XmlNodeList storyNodes = theXmlDocument.GetElementsByTagName("story");
            XmlNode theNode = storyNodes[0];
           
            int theId = Convert.ToInt32(theNode.Attributes["id"].InnerText);
            String theLink = theNode.Attributes["link"].InnerText;

            double theSubmitDateNumber = Convert.ToDouble(theNode.Attributes["submit_date"].InnerText);
            DateTime theSubmitDate = (new DateTime(1970, 1, 1, 0, 0, 0)).AddSeconds(theSubmitDateNumber);

            int theDigs = Convert.ToInt32(theNode.Attributes["digs"].InnerText);
            int theComments = Convert.ToInt32(theNode.Attributes["comments"].InnerText);
            String theHref = theNode.Attributes["href"].InnerText;
            String theStatus = theNode.Attributes["status"].InnerText;

            String theTitle = "";
            String theDescription = "";
            String theUser = "";
            String theTopicName = "";
            String theTopicShortName = "";
            String theContainerName = "";
            String theContainerShortName = "";
            XmlNodeList childNodes = theNode.ChildNodes;
            foreach (XmlNode childNode in childNodes)
            {
                if (childNode.Name.Equals("title"))
                {
                    theTitle = childNode.InnerText;
                }
                else if (childNode.Name.Equals("description"))
                {
                    theDescription = childNode.InnerText;
                }
                else if (childNode.Name.Equals("user"))
                {
                    theUser = childNode.Attributes["name"].InnerText;
                }
                else if (childNode.Name.Equals("topic"))
                {
                    theTopicName = childNode.Attributes["name"].InnerText;
                    theTopicShortName = childNode.Attributes["short_name"].InnerText;
                }
                else if (childNode.Name.Equals("container"))
                {
                    theContainerName = childNode.Attributes["name"].InnerText;
                    theContainerShortName = childNode.Attributes["short_name"].InnerText;
                }

            }

            Story theStory = new Story(theId, theLink, theSubmitDate, theDigs,
                                       theComments, theHref, theStatus, theTitle,
                                       theDescription, theUser, theTopicName, theTopicShortName,
                                       theContainerName, theContainerShortName);

            return theStory;

        }

        /// <summary>
        /// Returns a list of stories with details according to the list of IDs passes in (<paramref name="theStoryIds"/>)
        /// </summary>
        /// <param name="theStoryIds">List of int's that are IDs of stories that you wish to get details for</param>
        /// <returns>A list of 'Story' objects with all the details filled in</returns>
        public List<Story> GetMultipleStoryDetails(params int[] theStoryIds)
        {
            List<Story> theStories = new List<Story>();

            foreach (int id in theStoryIds)
            {
                theStories.Add(GetStoryDetails(id));
            }

            return theStories;
        }

        /// <summary>
        /// Gets a list of activities for a particular story specified by <paramref name="storyID"/> over a period specified by <paramref name="time"/>
        /// </summary>
        /// <param name="storyID">The ID of the story to get activity for</param>
        /// <param name="time">Over what time period to get data</param>
        /// <returns>Returns an activity group containing a list of activites filled with information (diggs, comments, etc)</returns>
        private ActivityGroup GetStoryActivity(int storyID, String time)
        {
            String diggStoriesConnectionString = "http://services.digg.com/story/"+Convert.ToString(storyID)+"/activity?period="+time;
            try
            {
                theXmlDocument.Load(diggStoriesConnectionString);
            }
            catch (XmlException e) { Console.WriteLine(e.Message); }

            XmlNodeList storyNodes = theXmlDocument.GetElementsByTagName("activity");
            ActivityGroup theGroup = null;
            foreach (XmlNode theNode in storyNodes)
            {
                String period = "";
                int diggs = -1;
                int comments = -1;
                DateTime start;
                DateTime end;
                if(theNode.Attributes["period"].InnerText.Equals("client"))
                {
                    period = theNode.Attributes["period"].InnerText;
                    diggs = Convert.ToInt32(theNode.Attributes["diggs"].InnerText);
                    comments = Convert.ToInt32(theNode.Attributes["comments"].InnerText);
                    start = (new DateTime(1970,1,1,0,0,0)).AddSeconds(Convert.ToDouble(theNode.Attributes["start"].InnerText));
                    end = (new DateTime(1970, 1, 1, 0, 0, 0)).AddSeconds(Convert.ToDouble(theNode.Attributes["end"].InnerText));

                    theGroup = new ActivityGroup(period, diggs, comments, start, end);
                }
                else if (theNode.Attributes["period"].InnerText.Equals(time) && theGroup != null)
                {
                    theGroup.AddActivity(new Activity(theNode.Attributes["period"].InnerText,
                                                      (new DateTime(1970,1,1,0,0,0)).AddSeconds(Convert.ToDouble(theNode.Attributes["start"].InnerText)),
                                                      (new DateTime(1970,1,1,0,0,0)).AddSeconds(Convert.ToDouble(theNode.Attributes["end"].InnerText)),
                                                      Convert.ToInt32(theNode.Attributes["diggs"].InnerText),
                                                      Convert.ToInt32(theNode.Attributes["comments"].InnerText)));
                }
                else
                {
                    return null;
                }

            }

            return theGroup;

        }

        /// <summary>
        /// Gets a list of activities for a particular story specified by <paramref name="storyID"/> over a period of an hour
        /// </summary>
        /// <param name="storyID">The ID of the story to get activity for</param>
        /// <returns>Returns an activity group containing a list of activites filled with information (diggs, comments, etc)</returns>
        public ActivityGroup GetStoryActivityByHour(int storyID)
        {
            return GetStoryActivity(storyID, "hour");
        }

        /// <summary>
        /// Gets a list of activities for a particular story specified by <paramref name="storyID"/> over a period of a day
        /// </summary>
        /// <param name="storyID">The ID of the story to get activity for</param>
        /// <returns>Returns an activity group containing a list of activites filled with information (diggs, comments, etc)</returns>
        public ActivityGroup GetStoryActivityByDay(int storyID)
        {
            return GetStoryActivity(storyID, "day");
        }


        /// <summary>
        /// Gets a list of the default number of stories within the specified topic
        /// </summary>
        /// <param name="topicName">The url safe version of the topic name to get stories from</param>
        /// <returns>The list of stories</returns>
        public List<Story> GetStoriesByTopic(String topicName)
        {
            
            String filter = "/topic/"+topicName;
            return GetStories(filter, _defaultSearchOptions); 
        }

        /// <summary>
        /// Gets a list of stories within the specified topic.
        /// </summary>
        /// <param name="topicName">The url safe version of the topic name to get stories from</param>
        /// <param name="theOptions">The options for the query</param>
        /// <returns>The list of stories</returns>
        public List<Story> GetStoriesByTopic(String topicName, SearchOptions theOptions)
        {
            String filter = "/topic/" + topicName;
            return GetStories(filter, theOptions);
        }

        private SearchOptions _defaultSearchOptions;
        private const int _defaultNumStories = 10;
        private XmlDocument theXmlDocument;
        private int _timeout = 10000;
    }
}
